
#include <stdlib.h>
#include <stdio.h>
#include <avr/io.h>
#include <avr/iom32.h> //Force eclipse to recognize this is a Mega32 device.
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include <string.h>

#include "spiProtocol.h"
#include "uart.h"

#define SETBIT(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define CLEARBIT(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
#define FLIPBIT(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
#define CHECKBIT(ADDRESS,BIT) (ADDRESS & (1<<BIT))

//#if(DEBUG==1)
//#define send_msg_P(s) uart_puts_P("\n\r[DEBUG MESSAGE] >> *"); uart_puts_P(s); _delay_ms(50)
//#define send_msg(s) uart_puts_P("\n\r[DEBUG MESSAGE] >> *"); uart_puts(s); _delay_ms(50)
//#else
#define send_msg_P(s) _delay_us(50)
#define send_msg(s) _delay_us(50)
//#endif

bool USE_LOWER_DELAY = true;
uint FLASHSIZE_KB;

void activateTargetControl(void)		{	spiInitialize();	DDRD |= BIT7;			PORTD &= (char)~BIT7;	}
void releaseTargetControl(void)			{	spiDeactivate();	DDRD &= (char)~BIT7;	PORTD &= (char)~BIT7;	}


void resetTarget(void)
{
	CLEARBIT(DDRD,PD7);
	CLEARBIT(PORTD,PD7);
	_delay_ms(200);
	releaseTargetControl();
	_delay_ms(100);
	SETBIT(DDRD,PD7);
	SETBIT(PORTD,PD7);
}

void spiInitialize(void)
{
	DDRB  &= (char)~(BIT4 | BIT7 | BIT5);

	DDRB  |= (BIT5 | BIT7);	// MOSI and SCK pins
	DDRB  |= BIT4;			// SS pin must be configured for output to not conflict with SPI operation

	SPCR |= (1<<SPE)|(1<<MSTR);
}

void spiDeactivate(void)
{
	DDRB &= (char)~(BIT2 | BIT3 | BIT4 | BIT5);
	SPCR &= (char)~((1<<SPE)|(1<<MSTR));
}

char readWriteSPIByte(char data)
{
	SPDR = data;
	loop_until_bit_is_set(SPSR, SPIF);

	// Some delay required otherwise SPI doesn't sync with target
	if(USE_LOWER_DELAY) {
		delay_us(8);
	}
	else {
		delay_us(50);
	}

	return SPDR;
}

bool checkSPI()
{
	return (enableProgramming() && 0x1E == readSignatureByte(0) && enableProgramming());
}

bool tryDifferentSPISpeeds()
{
	// Master, CLK/4
	//SPSR = 0;
	//SPCR = (1<<SPE)|(1<<MSTR);
	//if(checkSPI()) {
	//	send_msg_P("CLK/4\n\r");
	//	return true;
	//}

	// Master, CLK/8
	SPSR = 1;
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
	if(checkSPI()) {
		send_msg_P("CLK/8\n\r");
		return true;
	}


	// Master, CLK/16
	SPSR = 0;
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR0);
	if(checkSPI()) {
		send_msg_P("CLK/16\n\r");
		return true;
	}

	// Master, CLK/32
	SPSR = 1;
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
	if(checkSPI()) {
		send_msg_P("CLK/32\n\r");
		return true;
	}

	// Master, CLK/64
	SPSR = 0;
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
	if(checkSPI()) {
		send_msg_P("CLK/64\n\r");
		return true;
	}

	// Master, CLK/128
	SPSR = 0;
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
	if(checkSPI()) {
		send_msg_P("CLK/128\n\r");
		return true;
	}

	return false;
}

bool checkTargetCommunication()
{
	USE_LOWER_DELAY = true;
	if(tryDifferentSPISpeeds())
		return true;

	// Tried various clock divisions for SPI, now try putting more delay
	// between each SPI transfer
	USE_LOWER_DELAY = false;

	if(tryDifferentSPISpeeds())
		return true;

	return false;
}

bool writeEEPROM(uint address, char data)
{
	char b0 = 0xC0;
	char b1 = ((address >> 8) & 0x03);	// 10-bit MAX
	char b2 = (char)(address & 0x00FF);
	char b3 = data;

	sendPacket(&b0, &b1, &b2, &b3);

	char timeout = 200;	// 10ms timeout
	while(--timeout != 0) {
		if(targetReadyForNextCommand() && b1 == 0xC0)
			return true;
		delay_us(50);
	}
	return false;
}

bool readEEPROM(uint address, char* data)
{
	char b0 = 0xA0;
	char b1 = ((address >> 8) & 0x03);	// 10-bit MAX Address, MSB First
	char b2 = (char)(address & 0x00FF);
	char b3 = 0x00;

	*data = 0;
	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0xA0) {
		*data = b3;
		return true;
	}
	return false;
}

bool enableProgramming(void)
{
	releaseTargetControl();
	delay_us(100);
	activateTargetControl();
	delay_ms(21);		// Datasheet says wait at least 20ms

	char b0 = 0xAC;
	char b1 = 0x53;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	//if(b1 == 0xAC && b2 == 0x53) {
	if( b2 == 0x53) {
		return true;
	}
	else {
		releaseTargetControl();

		return 0;
	}
}

char readSignatureByte(char byteNumber)
{
	char b0 = 0x30;
	char b1 = 0x00;
	char b2 = byteNumber;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	// Signature 2 (byte1) contains memory information in lower 4 bits
	if(byteNumber == 1) {
		char i = b3 & 0x0f;
		FLASHSIZE_KB = 1;
		while(i-- != 0) {
			FLASHSIZE_KB = FLASHSIZE_KB * 2;
		}
	}

	if(b1 == 0x30) {
		return b3;
	}

	return 0;
}

bool eraseChip(void)
{
	if(enableProgramming()) {
		char b0 = 0xAC;
		char b1 = 0x80;
		char b2 = 0x00;
		char b3 = 0x00;

		sendPacket(&b0, &b1, &b2, &b3);

		// After erase command, need to release Reset
		enableProgramming();
		if (b1 == 0xAC && b2 == 0x80) {
			return 1;
		}
		else {

		}
	}
	return 0;
}

bool targetReadyForNextCommand(void)
{
	char b0 = 0xF0;
	char b1 = 0x00;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	// If LSB in b3 is 1, Programming operation is still pending
	if(b1 == 0xF0 && (b3 & 0x01) == 0)
		return true;
	return false;
}

char readLockByte(void)
{
	char b0 = 0x58;
	char b1 = 0x00;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0x58) {
		return b3;
	}

	return 0;
}

char readFuseHighByte(void)
{

	char b0 = 0x58;
	char b1 = 0x08;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0x58) {
		return b3;
	}

	return 0;
}

char readFuseLowByte(void)
{

	char b0 = 0x50;
	char b1 = 0x00;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0x50) {
		return b3;
	}

	return 0;
}

char readExtendedFuseByte(void)
{
	char b0 = 0x50;
	char b1 = 0x08;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0x50) {
		return b3;
	}

	return 0;
}

char readCalibrationByte(void)
{
	char b0 = 0x38;
	char b1 = 0x00;
	char b2 = 0x00;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0x38) {
		return b3;
	}

	return 0;
}

bool writeLockByte(char byte)
{
	char b0 = 0xAC;
	char b1 = 0xE0;
	char b2 = 0x00;
	char b3 = byte;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0xAC && b2 == 0xE0) {
		return true;
	}

	return false;
}

bool writeFuseHighByte(char byte)
{
	//byte &= (char)~(1<<5);	// Make sure we put SPI programming = 0, otherwise chip will never get programmed again!

	char b0 = 0xAC;
	char b1 = 0xA8;
	char b2 = 0x00;
	char b3 = byte;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0xAC && b2 == 0xA8) {
		return true;
	}

	return false;
}

bool writeFuseLowByte(char byte)
{
	char b0 = 0xAC;
	char b1 = 0xA0;
	char b2 = 0x00;
	char b3 = byte;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0xAC && b2 == 0xA0) {
		return true;
	}

	return false;
}

bool writeExtendedFuseByte(char byte)
{
	char b0 = 0xAC;
	char b1 = 0xA4;
	char b2 = 0x00;
	char b3 = byte;

	sendPacket(&b0, &b1, &b2, &b3);

	if(b1 == 0xAC && b2 == 0xA4) {
		return true;
	}

	return false;
}

void sendPacket(char *a, char *b, char *c, char *d)
{
	*a = readWriteSPIByte(*a);
	*b = readWriteSPIByte(*b);
	*c = readWriteSPIByte(*c);
	*d = readWriteSPIByte(*d);
}

void loadExtendedAddressByte(char byte)
{
	char b0 = 0x4D;
	char b1 = 0x00;
	char b2 = byte;
	char b3 = 0x00;

	sendPacket(&b0, &b1, &b2, &b3);
}

void loadProgramMemoryPage(unsigned char addressLSB, int word)
{
	char b1, b2, b3, b4;
	int tmp;
	tmp = word;
	//  Data LOW byte must be loaded first according to SPI Programming SPEC
	b1 = readWriteSPIByte(0x40);
	b2 = readWriteSPIByte(0x00);
	b3 = readWriteSPIByte(addressLSB);
	b4 = readWriteSPIByte((char)(tmp >> 8));

	// Now, load high byte
	b1 = readWriteSPIByte(0x48);
	b2 = readWriteSPIByte(0x00);
	b3 = readWriteSPIByte(addressLSB);
	b4 = readWriteSPIByte((char)(word));

	/*
	 	//  Data LOW byte must be loaded first according to SPI Programming SPEC
	b1 = readWriteSPIByte(0x40);
	b2 = readWriteSPIByte(0x00);
	b3 = readWriteSPIByte(addressLSB);
	b4 = readWriteSPIByte((char)(word & 0x00FF));

	// Now, load high byte
	b1 = readWriteSPIByte(0x48);
	b2 = readWriteSPIByte(0x00);
	b3 = readWriteSPIByte(addressLSB);
	b4 = readWriteSPIByte((char)(word >> 8));
	 */
}

void performPageWrite(unsigned int address)
{
	char b1, b2, b3, b4;

	b1 = readWriteSPIByte(0x4C);
	b2 = readWriteSPIByte((char)(address >> 8));
	b3 = readWriteSPIByte((char)(address & 0x00FF));
	b4 = readWriteSPIByte(0x00);

	//Atmel datasheet says 4.5ms wait minimum
	delay_ms(5.5);
}

unsigned int readProgramMemory(unsigned int address)
{
	unsigned int word = 0;
	char b1, b2, b3, b4;

	// Read high data byte first
	b1 = readWriteSPIByte(0x20);
	b2 = readWriteSPIByte((address>>8));
	b3 = readWriteSPIByte(address);
	b4 = readWriteSPIByte(0x00);
	word = b4;

	// Now read low data byte
	b1 = readWriteSPIByte(0x28);
	b2 = readWriteSPIByte((address>>8));
	b3 = readWriteSPIByte(address);
	b4 = readWriteSPIByte(0x00);

	word <<= 8;
	word |= b4;

	return word;
}

